using System;
using System.Collections.Generic;
using ACE.Board;
using System.Text;

namespace ACE.Engine
{
    class ACEEngine : IChessEngine
    {
        private const int DEFAULT_ALPHA = -10000;
        private const int DEFAULT_BETA = 10000;
        public const int MAX_PLY = 256; // An arbitrary cutoff point we should never achieve.

        protected ChessGame m_game;
        private ChessBoard m_board;
        private BoardEvaluator m_boardEvaluator;

        public event ACEEngineEventHandler OnMove;
        private bool m_bStopSearch;
        private long m_lStartTime;
        private long m_lStopTime;

        public Move NextMove;
        public Move PonderMove;
        private Book m_book;

        private PrincipleVariation m_pv;
        private int m_Ply;          // Number of 1/2 moves from root of search tree
        private int m_Nodes;        // Number of nodes searched.

        #region IChessEngine Members
        int m_whitetime;
        int m_blacktime;
        int m_whiteinc;
        int m_blackinc;
        /// <summary>
        /// right now this just returns a random move from the list of potential legal moves.
        /// </summary>
        /// <param name="board"></param>
        /// <returns></returns>
        public ACE.Board.Move GetNextMove(ACE.Board.ChessBoard board)
        {
            int check = 0;
            Random random = new Random();
            int highmove = board.PotentialMoves.Count;
            Move move = board.PotentialMoves[random.Next(highmove)];
            while (!board.IsMoveLegal(move))
            {
                if (++check > 10000)
                {
                    throw new ACEException("Engine unable to find a legal move.  Must be stupid.");
                }
                move = board.PotentialMoves[random.Next(highmove)];
            }
            return move;
        }

        public int WhiteTime
        {
            get
            {
                return m_whitetime;
            }
            set
            {
                m_whitetime = value;
            }
        }

        public int BlackTime
        {
            get
            {
                return m_blacktime;
            }
            set
            {
                m_blacktime = value;
            }
        }

        public int BlackInc
        {
            get
            {
                return m_blackinc;
            }
            set
            {
                m_blackinc = value;
            }
        }

        public int WhiteInc
        {
            get
            {
                return m_whiteinc;
            }
            set
            {
                m_whiteinc = value;
            }
        }

        protected int m_movetogo;
        public int MovesToGo
        {
            get
            {
                return m_movetogo;
            }
            set
            {
                m_movetogo = value;
            }
        }

        protected int m_searchdepth;
        public int SearchDepth
        {
            get
            {
                return m_searchdepth;
            }
            set
            {
                m_searchdepth = value;
            }
        }

        protected int m_searchnodes;
        public int SearchNodes
        {
            get
            {
                return m_searchnodes;
            }
            set
            {
                m_searchnodes = value;
            }
        }

        public virtual void go()
        {
            if (m_game != null)
            {
                FireOnMove(GetNextMove(m_game.Board).MoveString());
            }
        }

        #endregion

        /// <summary>
        /// default to 5;
        /// </summary>
        public int MaxSearchDepth;

        /// <summary>
        /// because of threading this may need to have locks put on it to make it thread safe.
        /// </summary>
        public bool StopSearch
        {
            get{ return m_bStopSearch; }
            set{ m_bStopSearch = true; }
        }


        //public bool StopSearch()
        //{
        //    m_bStopSearch = true;
        //}

        public ACEEngine(ChessGame game):this()
        {
            m_game = game;
        }


        public ACEEngine()
        {
            NextMove = null;
            PonderMove = null;
            m_book = new Book();
            m_pv = new PrincipleVariation();
            MaxSearchDepth = 5;
        }

        public ChessGame Game
        {
            get
            {
                return m_game;
            }
            set
            {
                m_game = value;
            }
        }

        /// <summary>
        /// Simple think.  Picks the best move from the list of potential moves.
        /// </summary>
        public void Think()
        {
            NextMove = null;
            PonderMove = null;
            int topMoveScore = 0;
            List<Move> candidates = new List<Move>();

            m_bStopSearch = false;
            DateTime dt = DateTime.Now;
            m_lStartTime = dt.Ticks;
            m_lStopTime = m_lStartTime + MAX_TIME-1000;

            Move bookMove = m_book.GetBookMove(m_game.Board.MoveHistory);
            if (bookMove != null)
            {
                NextMove = bookMove;
                return;
            }
            BoardEvaluator be = new BoardEvaluator(m_game.Board);
            int side = m_game.Board.SideToMove;
            List<Move> list = m_game.Board.PotentialMoves;
            for (int i = 0; i < list.Count; ++i)
            {
                Move tryMove = list[i];
                if (m_game.Board.MakeMove(tryMove))
                {
                    int eval = be.Evaluate(side);
                    if (candidates.Count==0)
                    {
                        topMoveScore = eval;
                        candidates.Add( tryMove );
                    }
                    else
                    {
                        if (eval > topMoveScore)
                        {
                            candidates.Clear();
                            candidates.Add( tryMove );
                            topMoveScore = eval;
                        }
                        if (eval == topMoveScore)
                        {
                            candidates.Add(tryMove); 
                        }
                    }
                    m_game.Board.TakeBackMove();
                }
            }
            if (candidates.Count == 0) return;
            if (candidates.Count == 1)
            {
                NextMove = candidates[0];
            }
            else
            {
                Random rnd = new Random();
                int indx=rnd.Next(candidates.Count);
                NextMove = candidates[indx];
            }
        }

        private void think()
        {
            m_board = Game.Board;
            m_boardEvaluator = new BoardEvaluator(m_board);

            m_bStopSearch = false;
            DateTime dt = DateTime.Now;
            m_lStartTime = dt.Ticks;
            m_lStopTime = m_lStartTime + MAX_TIME - 1000;

            // 1. Check Book for next move first.
            Move bookMove = m_book.GetBookMove(m_game.Board.MoveHistory);
            if (bookMove != null)
            {
                NextMove = bookMove;
                return;
            }

            // Move not found in book, we'll have to search on out.

            // 2. Initialize module vars.  These will be used in the search function.
            m_Ply = 0;
            m_Nodes = 0;
            m_pv.Clear();

            // 3.  For each ply depth, search for the best move.
            int searchPly = 1;
            while (!StopSearch)
            {
                int plyScore = NegaMaxSearch(DEFAULT_ALPHA, DEFAULT_BETA, searchPly);
                ++searchPly;
                if (searchPly > MaxSearchDepth)
                {
                    StopSearch = true;
                }   
            }

        }

        /// <summary>
        /// The actual nega max search algorithm
        /// </summary>
        /// <param name="alpha"></param>
        /// <param name="beta"></param>
        /// <param name="depth"></param>
        private int NegaMaxSearch(int alpha, int beta, int depth)
        {
            int innerAlpha = alpha;  // alpha value internal to this scope.

            // we're as deep as we want to be; call quiesce() to get
            // a reasonable score and return it.
            if (depth==0){
                return Quiesce(alpha, beta);
            }
            ++m_Nodes;

            
	        if ((m_Nodes & 1023) == 0){
		        KeepHouse();
            }

            // if not root position and position has been repeated, then return 0
            if (m_Ply > 0 && IsRepeat())
            {
                return 0;
            }

            if(m_Ply==MAX_PLY){
                return m_boardEvaluator.Evaluate();
            }

            if (m_board.InCheck())
            {
                ++depth;
            }

            // Generate the list of valid moves
            m_board.GeneratePotentialMoves();

            if (m_pv.FollowPV)
            {
                //sort pv
            }

            bool hasValidMove = false;
            
            // Loop through each move in the list
            List<Move> moveList = m_board.PotentialMoves;
            for (int i = 0; i < moveList.Count; ++i)
            {
                Move move = moveList[i];
                if (!m_board.MakeMove(move))
                    continue;  // Not a valid move
                ++m_Ply;       // Move was made, increase ply count.
                hasValidMove = true;

                int scr = NegaMaxSearch(-beta, -innerAlpha);
                m_board.TakeBackMove();
                --m_Ply;

                if (scr > innerAlpha)
                {
                    // this move caused a cut off.
                    Move.UpdateSearchHistory(move, depth);

                    if (scr > beta)
                        return beta;
                    innerAlpha = scr;


                }

            }


            
            
            return returnValue;


        }

        /// <summary>
        /// NegaMax search that only searches captures sequences.
        /// </summary>
        /// <param name="alpha"></param>
        /// <param name="beta"></param>
        /// <returns></returns>
        private int Quiesce(int alpha, int beta)
        {
            int returnValue = alpha;
            ++m_Nodes;


            if ((m_Nodes & 1023) == 0)
            {
                KeepHouse();
            }
            return returnValue;
        }

        private void KeepHouse()
        {

        }

        private bool IsRepeat()
        {
            // TODO, figure out if this position has been repeated in the current line.
            return false;
        }

        private void FireOnMove(string move)
        {
            if (OnMove != null)
            {
                ACEEventArgs args = new ACEEventArgs();
                args.values.Add(move);
                OnMove(this, args);
            }
        }
        private void FireOnMove(string move, string ponder)
        {
            if (OnMove != null)
            {
                ACEEventArgs args = new ACEEventArgs();
                args.values.Add(move);
                args.values.Add(ponder);
                OnMove(this, args);
            }
        }



        public int MAX_TIME
        {
            get
            {
                if (m_game.Board.SideToMove == 0)
                    return WhiteTime;
                else
                    return BlackTime;
            }
        }

    }

    public class ACEEventArgs : EventArgs
    {
        public List<string> values;
        public ACEEventArgs()
        {
            values = new List<string>();
        }
    }

    public delegate void ACEEngineEventHandler (object sender, ACEEventArgs e);

    /// <summary>
    /// The Principle Variation is basically a list of moves that makes the 
    /// most sense to play according to what has been searched.  The intention
    /// is to keep for every searched depth the best move continuation.  In most
    /// programs this is done with a simple 2 dimensional array.  In the spirit of
    /// .NET we use the collection classes to do this for us.  As the 
    /// </summary>
    class PrincipleVariation 
    {
        private Stack<Move> m_moveStack;
        Move m_bestMove;
        private int currentPly;
        private int maxply;
        private Move []pv;

        public bool FollowPV;

        public PrincipleVariation()
        {
            m_moveStack = new Stack<Move>();
            currentPly = -1;
            maxply = -1;
        }

        public void Clear()
        {

        }

        public void ClearStack()
        {
            m_moveStack.Clear();
        }

        /// <summary>
        /// Place move into the variation.
        /// </summary>
        /// <param name="move">The move to play</param>
        /// <param name="ply">The move ply</param>
        /// <param name="depth">The max depth of the variation</param>
        public void PushMove(Move move, int ply)
        {
            // get the stack in proper position to push move onto it.
            if (ply >= maxply)
            {
                // this is the deepest ply so far.  Clear the stack.
                maxply = ply;
                m_moveStack.Clear();
                currentPly = ply;
            }
            else
            {
                if (ply >= currentPly)
                {
                    int dif = ply - currentPly + 1;
                    for (int i = 0; i < dif; ++i) m_moveStack.Pop();
                }
                currentPly = ply;
            }
            m_moveStack.Push(move);
            if (ply == 0)
            {
                m_bestMove = move;
                pv = Variation();
            }
        }

        public Move[] Variation()
        {
            return m_moveStack.ToArray();
        }
    }

}
